package vip.yugu.personality.configurer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.StringUtils;
import vip.yugu.personality.filter.CustomClientCredentialsTokenEndpointFilter;
import vip.yugu.personality.granter.CustomRefreshTokenGranter;
import vip.yugu.personality.granter.MobilePasswordCustomTokenGranter;
import vip.yugu.personality.granter.MobileSmsCustomTokenGranter;
import vip.yugu.personality.model.CustomUser;
import vip.yugu.personality.service.CustomUserDetailsService;
import java.util.*;

/**
 * @Author: yugu
 * @CreateDate: 2019/9/6
 * @Description: 授权服务配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    private TokenStoreType tokenStoreType = TokenStoreType.IN_MEMORY;

    private enum TokenStoreType {
        IN_MEMORY, REDIS, JWT
    }

    @Autowired
    private RedisConnectionFactory connectionFactory;

    //vip.yugu.personality.CustomAuthenticationEntryPoint
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("demo-oauth2");
        ((DefaultAccessTokenConverter) accessTokenConverter.getAccessTokenConverter()).setUserTokenConverter(new DefaultUserAuthenticationConverter() {
            @Override
            public Authentication extractAuthentication(Map<String, ?> map) {
                CustomUser customUser = new CustomUser();
                BeanMap.create(customUser).putAll(map);
                Object authorities = map.get("authorities");
                if (authorities instanceof String) {
                    customUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities));
                } else if (authorities instanceof Collection) {
                    customUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection) authorities)));
                }
                return new PreAuthenticatedAuthenticationToken(customUser, null, customUser.getAuthorities());
            }
        });
        return accessTokenConverter;
    }

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private AuthorizationServerEndpointsConfiguration configuration;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("personality")
                .secret(passwordEncoder().encode("personality"))
                .scopes("all")
                .authorizedGrantTypes("client_credentials","refresh_token", "password", "authorization_code", "pwd", "sms", "client_credentials", "refresh_token");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        String path = "/oauth/token";
        try {
            // 获取自定义映射路径，比如 ((AuthorizationServerEndpointsConfigurer) endpoints).pathMapping("/oauth/token", "/my/token");
            path = configuration.oauth2EndpointHandlerMapping().getServletPath(path);
        } catch (Exception e) {
        }
        CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security, path);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);

        security.authenticationEntryPoint(authenticationEntryPoint);
        security.addTokenEndpointAuthenticationFilter(endpointFilter);

        security.tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore());

        List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getAuthorizationCodeServices(), endpoints.getTokenStore(), endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
        endpoints.tokenEnhancer(new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
                DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
                CustomUser user = (CustomUser) oAuth2Authentication.getPrincipal();
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("nickname", user.getNickname());
                map.put("mobile", user.getMobile());
                map.put("avatar", user.getAvatar());
                token.setAdditionalInformation(map);

                if (tokenStoreType == TokenStoreType.JWT) {
                    token = (DefaultOAuth2AccessToken) jwtAccessTokenConverter().enhance(oAuth2AccessToken, oAuth2Authentication);
                } else {
                    token.setValue(buildTokenValue());
                    if (token.getRefreshToken() != null) {
                        if (token.getRefreshToken() instanceof DefaultExpiringOAuth2RefreshToken) {
                            DefaultExpiringOAuth2RefreshToken refreshToken = (DefaultExpiringOAuth2RefreshToken) token.getRefreshToken();
                            token.setRefreshToken(new DefaultExpiringOAuth2RefreshToken(buildTokenValue(), refreshToken.getExpiration()));
                        } else {
                            token.setRefreshToken(new DefaultOAuth2RefreshToken(buildTokenValue()));
                        }
                    }
                }

                return token;
            }

            public String buildTokenValue() {
                String tokenValue = UUID.randomUUID().toString() + UUID.randomUUID().toString();
                return tokenValue;
            }

        });

    }

    private List<TokenGranter> getTokenGranters(AuthorizationCodeServices authorizationCodeServices, TokenStore tokenStore, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        return new ArrayList<>(Arrays.asList(
                new CustomRefreshTokenGranter(tokenStore, tokenServices, clientDetailsService, requestFactory),
                new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory),
                new MobilePasswordCustomTokenGranter(userDetailsService, tokenServices, clientDetailsService, requestFactory),
                new MobileSmsCustomTokenGranter(userDetailsService, tokenServices, clientDetailsService, requestFactory)
        ));
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public TokenStore tokenStore() {
        switch (tokenStoreType) {
            case REDIS:
                return new RedisTokenStore(connectionFactory);
            case JWT:
                return new JwtTokenStore(jwtAccessTokenConverter());
        }
        return new InMemoryTokenStore();
    }

}
